import moment from "moment";

export namespace StXML {
    export interface StAttr {
        [attr: string]: string | boolean;
    }

    export interface StAttrMap {
        name: string;
        value: string | boolean;
    }

    export interface BaseNode {
        tagName: string;
        attrsList: StAttrMap[];
        attrMap: StAttr;
        parent: BaseNode;
        children: Node[];
        isText: false;
    }
    export interface TextNode {
        isText: true;
        text: string;
        parent: BaseNode;
    }
    export type Node = BaseNode | TextNode;
}
const tagNameReg = `\\w\\-\\.`;

function createNodeByTagName(tagName: string): StXML.BaseNode {
    return {
        tagName,
        attrsList: [],
        attrMap: {},
        parent: null,
        children: [],
        isText: false,
    };
}

function createTextNode(text: string, parentNode: StXML.BaseNode = null): StXML.TextNode {
    return {
        isText: true,
        text,
        parent: parentNode,
    };
}

function matchNodeStart(xml: string) {
    const reg = new RegExp(`^\\<([${tagNameReg}]+)`);
    const result = xml.match(reg);
    if (result) {
        return {
            node: createNodeByTagName(result[1]),
            len: result[0].length,
        };
    } else {
        return false;
    }
}

function matchNodeEnd(xml: string) {
    const reg = new RegExp(`^\\<\\/[${tagNameReg}]+\\>`);
    const result = xml.match(reg);
    if (result) {
        return {
            len: result[0].length,
        };
    }
    return false;
}

function matchStartEnd(xml: string) {
    const reg = /^(\/)?\>/;
    const result = xml.match(reg);
    if (result) {
        return {
            selfClosed: !!result[1],
            len: result[0].length,
        };
    }
    return false;
}

function matchTextNode(xml: string) {
    const reg = /^([^<]+)/;
    const result = xml.match(reg);
    if (result) {
        return {
            len: result[0].length,
            text: result[1],
        };
    }
    return false;
}

function matchAttr(xml: string) {
    let reg = /^([\w\-:.@{}]+)\s*=?\s*(?:"([^"]*)"|'([^']*)')?/;
    let result = xml.match(reg);
    if (result) {
        return {
            attr: {
                name: result[1],
                value: result[2],
            },
            len: result[0].length,
        };
    }
    reg = /^([\w\-:.@]+)/;
    result = xml.match(reg);
    if (result) {
        return {
            attr: {
                name: result[1],
                value: true,
            },
            len: result[0].length,
        };
    }
    return false;
}

export function parseXML(xml: string) {
    xml = xml.trim();
    let nowNode: StXML.BaseNode;
    let root: StXML.BaseNode;
    let inNodeStart: boolean = false;

    function whenInNodeStart(xmlPart: string): string {
        const startEnd = matchStartEnd(xmlPart);
        if (startEnd) {
            xmlPart = xmlPart.slice(startEnd.len);
            inNodeStart = false;
            if (startEnd.selfClosed) {
                nowNode = nowNode.parent as StXML.BaseNode;
            }
            xmlPart = xmlPart.trim();
        } else {

            const attrMatch = matchAttr(xmlPart);
            if (attrMatch) {
                xmlPart = xmlPart.slice(attrMatch.len);
                nowNode.attrsList.push(attrMatch.attr);
                nowNode.attrMap[attrMatch.attr.name] = attrMatch.attr.value;
                xmlPart = xmlPart.trim();
            } else {

                console.error(`123has error:\n${xmlPart.slice(0, 100)}`);
                console.log(nowNode);
                throw new Error("--");
            }
        }
        return xmlPart;
    }

    while (xml && xml.length) {
        xml = xml.trim();
        if (nowNode && nowNode.tagName && nowNode.tagName.toLowerCase() === "script") {
            if (inNodeStart) {
                xml = whenInNodeStart(xml);
            } else {
                let matchRule = xml.match(/^[^<]+/i);
                if (matchRule) {
                    nowNode.children.push(createTextNode(matchRule[0], nowNode));
                    xml = xml.slice(matchRule[0].length);
                } else {
                    matchRule = xml.match(/^<\/script>/i);
                    if (matchRule) {
                        xml = xml.slice(matchRule[0].length);
                        nowNode = nowNode.parent;
                    } else {
                        nowNode.children.push(createTextNode("<", nowNode));
                        xml = xml.slice(1);
                    }
                }
            }
            continue;
        }
        const start = matchNodeStart(xml);
        if (start) { // <...
            xml = xml.slice(start.len);
            start.node.parent = nowNode;
            if (nowNode && nowNode.children) {
                nowNode.children.push(start.node);
            }
            nowNode = start.node;
            if (!root) {
                root = nowNode;
            }
            xml = xml.trim();
            inNodeStart = true;

            // console.log(xml.length)
            // xml = ''
        } else {
            if (inNodeStart) {
                xml = whenInNodeStart(xml);
                // xml.trim();
                // continue;
            } else {
                const nodeEnd = matchNodeEnd(xml);
                if (nodeEnd) {
                    nowNode = nowNode.parent as StXML.BaseNode;
                    xml = xml.slice(nodeEnd.len);
                    xml = xml.trim();
                } else {
                    const textRes = matchTextNode(xml);
                    let text: string;
                    if (textRes) {
                        text = textRes.text;
                    } else {
                        text = xml.slice(0, 1);
                    }
                    xml = xml.slice(text.length);
                    const textNode = createTextNode(text, nowNode);
                    nowNode.children.push(textNode);
                    xml = xml.trim();
                }
            }
        }
    }

    return root;
}

export function parseTonariYJObjToArticleList(obj: StXML.BaseNode) {
    const channelObj = obj.children[0] as StXML.BaseNode;
    return channelObj.children.filter((item) => {
        if (item.isText) {
            return false;
        } else {
            const baseNode = item as StXML.BaseNode;
            return baseNode.tagName === "item";
        }
    }).map((item) => {
        const baseNode = item as StXML.BaseNode;
        return makeArticleItemByTonariYJObj(baseNode);
    });
}

function makeArticleItemByTonariYJObj(itemNode: StXML.BaseNode): {
    title: string,
    link: string,
    pubDate: number,
    episode: number,
} {
    const list = itemNode.children;
    const map = {
        title: "",
        link: "",
        pubDate: moment().valueOf(),
        episode: 0,
    };
    list.forEach((item) => {
        const baseNode = item as StXML.BaseNode;
        const textNode = baseNode.children[0] as StXML.TextNode;
        if (textNode) {
           const text = textNode.text;
           if (baseNode.tagName === "guid") {
               const res = text.match(/\d+/);
               if (res) {
                   map.episode = parseInt(res[0]);
               }
           } else if (baseNode.tagName === "pubDate") {
               map.pubDate = moment(text).valueOf();
           } else if (map.hasOwnProperty(baseNode.tagName)) {
               (map as any)[baseNode.tagName] = text;
           }
        }
    });
    return map;
}

export function removeSpecialTag(xml: string, startTag: string = "<!--", endTag: string = "-->"): string {
    const indexStart = xml.indexOf(startTag);
    let indexEnd;
    if (indexStart >= 0) {
        indexEnd = xml.slice(indexStart).indexOf(endTag);
        const frontPart = xml.slice(0, indexStart);
        const endPart = xml.slice(indexEnd + indexStart + endTag.length);
        xml = frontPart + endPart;
    }
    return xml;
}

export function removeSpecialTagFrom(xml: string): string {
    while (xml.length) {
        let indexStart = xml.indexOf("<!--");
        if (indexStart >= 0) {
            xml = removeSpecialTag(xml);
            continue;
        }
        indexStart = xml.indexOf("<%");
        if (indexStart >= 0) {
            xml = removeSpecialTag(xml, "<%", "%>");
            continue;
        }
        if (indexStart < 0) {
            break;
        }
    }
    return xml;
}

export function reduceHTML2SimpleXML(html: string): string {
    let xml = html
        .replace(/[\r\n]+/g, "\\r\\n")
        .replace(/^<![^>]+>/mgi, "")
        .replace(/<(img|input|meta|link|br)[^>]*>/mgi, "");
    xml = removeSpecialTagFrom(xml);
    return xml.replace(/\\[rn]/g, "\r\n");
}
